home *** CD-ROM | disk | FTP | other *** search
Wrap
/* * Copyright (c) 1990, 1991 Stanford University * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose is hereby granted without fee, provided * that (i) the above copyright notices and this permission notice appear in * all copies of the software and related documentation, and (ii) the name * Stanford may not be used in any advertising or publicity relating to * the software without the specific, prior written permission of * Stanford. * * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT * ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ /* $Header: /Source/Media/collab/TimeLine/RCS/drawCanvas.c,v 1.0 91/09/30 16:54:25 chua Exp Locker: drapeau $ */ /* $Log: drawCanvas.c,v $ * Revision 1.0 91/09/30 16:54:25 chua * Update to version 1.0 * * Revision 0.49 91/09/26 18:00:18 chua * In MouseDownEvents, if the click was on a note, do not deselect it if it * was already selected. * * Revision 0.48 91/09/23 17:08:50 chua * Deselect any selected note when positioning the playback head. * * Revision 0.47 91/09/20 12:59:33 chua * In DrawPlaybackHead, add back 2 braces in the while (sec >= 60) statement, * which were accidentally taken out. * * Revision 0.46 91/09/19 17:28:42 chua * Make sure that variables are initialized properly. Change formatting slightly, * so that (if, for, while) statements with only one statement in them will not have * braces. * * Revision 0.45 91/09/16 14:38:42 chua * In DrawCanvasEventHandler, when checking if an event is a keyboard event, also check * if the erase key is being pressed, other than checking for the left keypad and * ascii keys. * * Revision 0.44 91/08/21 16:52:09 chua * Modified DrawCanvasEventHandler to take care of double click events. * Double clicking on a note will bring up the notes info popup window. * * Revision 0.43 91/08/20 16:15:51 chua * In MouseUpEvents, if the mouse was dragged over a region, check if the region is too small. * If it is, just position the playback head instead of highlighting a region. * * Revision 0.42 91/08/19 19:17:55 chua * Added a noteOffset variable to keep track of where the mouse is in a note when * dragging it. * * Revision 0.41 91/08/16 16:57:51 chua * *** empty log message *** * * Revision 0.40 91/08/16 16:56:59 chua * This version contains the ScrollTimerNotify routine and also the DrawCanvas repaint * and event handlers. * */ static char drawCanvasrcsid[] = "$Header: /Source/Media/collab/TimeLine/RCS/drawCanvas.c,v 1.0 91/09/30 16:54:25 chua Exp Locker: drapeau $"; #include "main.h" #include <sys/time.h> static int timerOn = 0; /* Indicates if the scrolling timer is on */ static int wasLeftDown = 0; static int selectNote = 0; /* Indicates if a note has been selected and may be dragged */ static Instrument *currentInst; /* Pointer to the chosen instrument node */ static struct itimerval timer; /* Timer for playback purposes */ static TimeLineFramePtr currenttlFrame; /* Pointer to current TimeLine document being played */ static int noteOffset = 0; /* Indicates where the mouse is on a note when dragging it. */ /* * This function handles keyboard events. Please refer to the comment heading for the DrawCanvasEventHandler for more information. */ void CheckKeyboardEvents(tlFrame, event, item) TimeLineFramePtr tlFrame; Event *event; Menu_item item; { switch (event_action(event)) { case ACTION_ERASE_CHAR_BACKWARD: DeleteNotesFromList(1, tlFrame); break; case ACTION_COPY: CopyHandler(item, MENU_NOTIFY); break; case ACTION_PASTE: PasteHandler(item, MENU_NOTIFY); break; case ACTION_CUT: CutHandler(item, MENU_NOTIFY); break; default: break; } } /* * This function handles mouse down events. Please refer to the comment heading for the DrawCanvasEventHandler for more information. */ void MouseDownEvents(tlFrame, xPos, yPos, event) TimeLineFramePtr tlFrame; int xPos; int yPos; Event *event; { int noteX; int noteSelect; int done; int doubleClick; selectNote = 0; timerOn = 0; wasLeftDown = 1; scrollHorStart = xv_get(tlFrame->DrawScrollbarHor, SCROLLBAR_VIEW_START); /* Get the canvas window horizontal and vertical start and end points */ scrollHorEnd = scrollHorStart + xv_get(tlFrame->DrawScrollbarHor, SCROLLBAR_VIEW_LENGTH); scrollVerStart = xv_get(tlFrame->DrawScrollbarVer, SCROLLBAR_VIEW_START); scrollVerEnd = scrollHorStart + xv_get(tlFrame->DrawScrollbarVer, SCROLLBAR_VIEW_LENGTH); if (tlFrame->areaSelected) /* Clear the previously selected area */ XFillRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart, tlFrame->startY, (tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel, tlFrame->endY - tlFrame->startY); DrawPlaybackHead(-1, tlFrame); tlFrame->areaSelected = 0; SetStartEndRegion(tlFrame, 0, 0); tlFrame->startX = xPos; tlFrame->startY = yPos; tlFrame->endX = xPos; tlFrame->endY = yPos; done = 0; currentInst = tlFrame->instHead; while (currentInst != NULL && !done) /* Find which instrument has been selected */ { if (event_y(event) >= currentInst->cableStart - NoteHeight/2 && event_y(event) <= currentInst->cableStart + NoteHeight/2) done = 1; else currentInst = currentInst->next; } if (currentInst != NULL) { wasLeftDown = 0; if (event_id(event) == MS_LEFT) /* Check if left mouse button is pressed */ { doubleClick = CheckDoubleClick(event, tlFrame); noteX = event_x(event); noteSelect = CheckNoteSelected(currentInst, (noteX + tlFrame->canvasStart) * tlFrame->zoomLevel, tlFrame, doubleClick); if (noteSelect == 0) { if (tlFrame->gridSpacing > 0) /* Snap to grid line if necessary */ noteX = noteX - noteX % tlFrame->gridSpacing; noteX = (noteX + tlFrame->canvasStart) * tlFrame->zoomLevel; AddandDisplayNewNote(currentInst, noteX, tlFrame); } else /* Note selected. May be dragging it */ { if (doubleClick == OK) ShowInfoWindow(currentInst, tlFrame); if (xv_get(tlFrame->TimeLine_window->controls, PANEL_CLIENT_DATA) != 0) { wasLeftDown = 1; selectNote = 1; noteOffset = event_x(event) - ((currentInst->infoNote->start / tlFrame->zoomLevel) - tlFrame->canvasStart); } } } else if (event_id(event) == MS_MIDDLE && /* Middle button is pressed. Delete a note */ xv_get(tlFrame->TimeLine_window->controls, PANEL_CLIENT_DATA) != 0) /* Only if this is not the clipboard document */ DeleteNote(currentInst, xPos, tlFrame); } } /* * This function handles mouse up events. Please refer to the comment heading for the DrawCanvasEventHandler for more information. */ void MouseUpEvents(tlFrame, xPos) TimeLineFramePtr tlFrame; int xPos; { if (wasLeftDown > 1) /* Some dragging has been done */ { if (timerOn == 1) { timerOn = 0; notify_set_itimer_func(currenttlFrame->TimeLine_window->window, /* Turn timer off */ NOTIFY_FUNC_NULL, ITIMER_REAL, NULL, NULL); } if (selectNote == 0) /* Draw the selected region */ { XDrawRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart, tlFrame->startY, (tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel, tlFrame->endY - tlFrame->startY); DeselectNote(tlFrame); /* Deselect any currently selected note first */ if (abs((tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel) < DistanceThreshold) DrawPlaybackHead(tlFrame->endX, tlFrame); else DrawSelectArea(tlFrame); } else /* Draw the note at the new position */ DrawMoveNote (currentInst, tlFrame, noteOffset); } else if (wasLeftDown == 1) { /* Draw playback head as mouse was not dragged */ if (selectNote == 0) { DeselectNote(tlFrame); /* Deselect any currently selected note first */ DrawPlaybackHead(xPos, tlFrame); } if (tlFrame->noteSelected == 1) /* If note was selected, set the area selected to that of the note */ { tlFrame->startX = tlFrame->startnoteX; tlFrame->endX = tlFrame->endnoteX; tlFrame->startY = tlFrame->startnoteY; tlFrame->endY = tlFrame->endnoteY; } } wasLeftDown = 0; } /* * This function is the timer notify procedure to check if scrolling of the canvas needs to be done when the user is attempting to select an area. * This occurs when the user drags the mouse off the canvas. The canvas should scroll even if the mouse is held still, which is why a timer procedure * is needed. The function will check if we are dragging an area or a selected note. */ Notify_value ScrollTimerNotify() { Rect *r; int x, y; int xStart, xLength; int yStart; xStart = (int) xv_get (currenttlFrame->DrawScrollbarHor, SCROLLBAR_VIEW_START); yStart = (int) xv_get (currenttlFrame->DrawScrollbarVer, SCROLLBAR_VIEW_START); xLength = xv_get(currenttlFrame->DrawScrollbarHor, SCROLLBAR_VIEW_LENGTH); r = (Rect *) xv_get(currenttlFrame->TimeLine_window->DrawCanvas, WIN_MOUSE_XY); x = r->r_left + xStart; if (x > xStart + xLength) x = xStart + xLength; if (x < xStart) x = xStart; y = r->r_top + yStart; y = y - y % (IconHeight + IconGap); if (selectNote == 1) /* Dragging a note */ { XDrawRectangle(currenttlFrame->dpyDraw, currenttlFrame->xidDraw, currenttlFrame->gcLine, (currenttlFrame->startX / currenttlFrame->zoomLevel) - currenttlFrame->canvasStart - noteOffset, currentInst->cableStart - NoteHeight/2, (currentInst->infoNote->ms->duration * PixelsPerSecond) / (2 * currenttlFrame->zoomLevel), NoteHeight); currenttlFrame->startX = (x + currenttlFrame->canvasStart) * currenttlFrame->zoomLevel; if (currenttlFrame->startX / currenttlFrame->zoomLevel - noteOffset < 0) currenttlFrame->startX = noteOffset * currenttlFrame->zoomLevel; } else /* Dragging an area */ { XDrawRectangle(currenttlFrame->dpyDraw, currenttlFrame->xidDraw, currenttlFrame->gcLine, (currenttlFrame->startX / currenttlFrame->zoomLevel) - currenttlFrame->canvasStart, currenttlFrame->startY, (currenttlFrame->endX - currenttlFrame->startX) / currenttlFrame->zoomLevel, currenttlFrame->endY - currenttlFrame->startY); currenttlFrame->endX = (x + currenttlFrame->canvasStart) * currenttlFrame->zoomLevel; if (y < currenttlFrame->startY) /* If the new Y position is smaller (higher) than the start position, */ currenttlFrame->endY = y; else currenttlFrame->endY = y + (IconHeight + IconGap); if (currenttlFrame->endY > (currenttlFrame->numberOfApps * (IconHeight + IconGap))) /* Set the maximum y position to be the bottom line of the last instrument track */ currenttlFrame->endY = currenttlFrame->numberOfApps * (IconHeight + IconGap); else if (currenttlFrame->endY < 0) /* Check that the Y position does not go off the canvas */ currenttlFrame->endY = 0; } if (x > scrollHorStart && x < scrollHorEnd && currenttlFrame->endY > scrollVerStart && currenttlFrame->endY < scrollVerEnd) { timerOn = 0; notify_set_itimer_func(currenttlFrame->TimeLine_window->window, /* Turn off the timer */ NOTIFY_FUNC_NULL, ITIMER_REAL, NULL, NULL); } CheckHorizontalScrolling(x, currenttlFrame); CheckVerticalScrolling(currenttlFrame->endY, currenttlFrame); if (selectNote == 1) XDrawRectangle(currenttlFrame->dpyDraw, currenttlFrame->xidDraw, currenttlFrame->gcLine, (currenttlFrame->startX / currenttlFrame->zoomLevel) - currenttlFrame->canvasStart - noteOffset, currentInst->cableStart - NoteHeight/2, (currentInst->infoNote->ms->duration * PixelsPerSecond) / (2 * currenttlFrame->zoomLevel), NoteHeight); else XDrawRectangle(currenttlFrame->dpyDraw, currenttlFrame->xidDraw, currenttlFrame->gcLine, (currenttlFrame->startX / currenttlFrame->zoomLevel) - currenttlFrame->canvasStart, currenttlFrame->startY, (currenttlFrame->endX - currenttlFrame->startX) / currenttlFrame->zoomLevel, currenttlFrame->endY - currenttlFrame->startY); return NOTIFY_DONE; } /* * Event callback function for `DrawCanvas'. * This function will first check if the timeline is in stop mode. If not, it will return. * If the mouse has just entered the canvas, the canvas will grab the keyboard focus so that any key typed would be detected by it. * If the Delete or Backspace key is pressed, this has the same effect as choosing the Delete item from the edit menu. * If the Copy, Paste or Cut button on the left keypad is pressed, the relevant edit functions are performed. * If the left mouse button is pressed, the function checks if it is pressed on the instrument cable (with the allowance of the height of a note). * If so, it will add a new note starting at where the mouse was pressed, if there was no note already existing at where the mouse click was. * If a note exist, this note will be selected. * If not, it will reposition the playback head to where the mouse was pressed. * If the mouse was dragged, a shaded rectangle area covering the space over which the mouse was dragged will be drawn. This represents the selected area * on which edit functions (cut, paste, etc) can now be done. Also, if an area was selected, any previously selected note would be deselected. This * is to avoid confusion, since the edit functions can also apply to a selected note. * Dragging can also be applied to a note. This is indicated by the variable selectNote, which is set to 1 if a note has been selected, 0 otherwise. * A check is also made to see if the mouse was dragged off the canvas. If so, a timer is started and this timer is called every 1/5th of a second to * check if scrolling needs to be done. * If the middle mouse button was pressed and it is near the chosen instrument cable, the note that the mouse was on will be deleted. If there is no * note, nothing will be done. A note will only be deleted if it does not belong on the clipboard document, meaning clicking the middle button on the * clipboard canvas will have no effect. */ Notify_value DrawCanvasEventHandler(win, event, arg, type) Xv_window win; Event *event; Notify_arg arg; Notify_event_type type; { int xPos, yPos; TimeLineFramePtr tlFrame; TimeLine_window_objects *ip = (TimeLine_window_objects *) xv_get(win, XV_KEY_DATA, INSTANCE); tlFrame = TimeLineWindow[xv_get(ip->controls, PANEL_CLIENT_DATA)]; xPos = (event_x(event) + tlFrame->canvasStart) * tlFrame->zoomLevel; yPos = event_y(event) - event_y(event) % (IconHeight + IconGap); /* Move y position to the nearest track starting y position */ if (tlFrame->status == StopMode) /* Only execute the following if the TimeLine is in Stop mode */ { if (event_id(event) == LOC_WINENTER) /* Grab the keyboard focus for the canvas */ win_set_kbd_focus(win, xv_get(win, XV_XID)); else if ((event_is_ascii(event) || event_is_key_left(event) || event_action(event) == ACTION_ERASE_CHAR_BACKWARD) && event_is_down(event)) CheckKeyboardEvents(tlFrame, event, ip->documentButton); else if (event_is_down(event) /* Left or middle mouse button is down */ && (event_id(event) == MS_LEFT || event_id(event) == MS_MIDDLE)) MouseDownEvents(tlFrame, xPos, yPos, event); else if (event_is_up(event)) MouseUpEvents(tlFrame, xPos); else if (event_id(event) == LOC_DRAG && wasLeftDown && timerOn == 0) { if (event_x(event) <= scrollHorStart || event_x(event) >= scrollHorEnd || event_y(event) <= scrollVerStart || event_y(event) >= scrollVerEnd) { currenttlFrame = tlFrame; timerOn = 1; timer.it_value.tv_sec = 0; /* Set the timer values to notify every 1/5th of a second */ timer.it_value.tv_usec = 200000; /* The timer notify procedure will check to see if scrolling needs to be done */ timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 200000; notify_set_itimer_func(tlFrame->TimeLine_window->window, ScrollTimerNotify, ITIMER_REAL, &timer, NULL); } else { if (selectNote == 0) { XDrawRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart, tlFrame->startY, (tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel, tlFrame->endY - tlFrame->startY); tlFrame->endX = xPos; if (yPos < tlFrame->startY) /* If the new Y position is smaller (higher) than the start position, */ tlFrame->endY = yPos; else tlFrame->endY = yPos + (IconHeight + IconGap); if (tlFrame->endY > (tlFrame->numberOfApps * (IconHeight + IconGap))) /* Set the maximum y position to be the bottom line of the last instrument track */ tlFrame->endY = tlFrame->numberOfApps * (IconHeight + IconGap); else if (tlFrame->endY < 0) /* Check that the Y position does not go off the canvas */ tlFrame->endY = 0; XDrawRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart, tlFrame->startY, (tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel, tlFrame->endY - tlFrame->startY); } else { if (wasLeftDown > 1) XDrawRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart - noteOffset, currentInst->cableStart - NoteHeight/2, (currentInst->infoNote->ms->duration * PixelsPerSecond) / (2 * tlFrame->zoomLevel), NoteHeight); tlFrame->startX = xPos; if (tlFrame->startX / tlFrame->zoomLevel - noteOffset < 0) tlFrame->startX = noteOffset * tlFrame->zoomLevel; XDrawRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart - noteOffset, currentInst->cableStart - NoteHeight/2, (currentInst->infoNote->ms->duration * PixelsPerSecond) / (2 * tlFrame->zoomLevel), NoteHeight); } } wasLeftDown ++; } } return notify_next_event_func(win, (Notify_event) event, arg, type); } /* * Repaint callback function for `DrawCanvas'. * The function clear the whole Draw canvas and redraw all the instrument cables and notes. * Since the playback head has been cleared, it will be redrawn at its last position after the new notes are drawn. */ void DrawCanvasRepaintHandler(canvas, paint_window, display, xid, rects) Canvas canvas; Xv_window paint_window; Display *display; Window xid; Xv_xrectlist *rects; { Instrument *instrument; int templastX; TimeLineFramePtr tlFrame; TimeLine_window_objects *ip = (TimeLine_window_objects *) xv_get(canvas, XV_KEY_DATA, INSTANCE); tlFrame = TimeLineWindow[xv_get(ip->controls, PANEL_CLIENT_DATA)]; XClearWindow(tlFrame->dpyDraw, tlFrame->xidDraw); /* Clear the whole display */ DrawGrid (tlFrame); for (instrument = tlFrame->instHead; instrument != NULL; instrument = instrument->next) InstrumentDraw(instrument, tlFrame); templastX = tlFrame->lastX; /* Store the last playback head position */ tlFrame->lastX = -1; DrawPlaybackHead (templastX, tlFrame); if (xv_get(tlFrame->TimeLine_window->controls, PANEL_CLIENT_DATA) == 0) /* Draw the ending line for the clipboard frame */ { XDrawLine(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gc, clipDuration, 0, clipDuration, tlFrame->numberOfApps * 72 + FirstCableYPosition); XDrawLine(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gc, clipDuration + 2, 0, clipDuration + 2, tlFrame->numberOfApps * 72 + FirstCableYPosition); } if (tlFrame->areaSelected && (tlFrame->startX / tlFrame->zoomLevel >= tlFrame->canvasStart) && (tlFrame->startX / tlFrame->zoomLevel <= tlFrame->canvasStart + tlFrame->TimeLineLength)) XFillRectangle(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, (tlFrame->startX / tlFrame->zoomLevel) - tlFrame->canvasStart, tlFrame->startY, (tlFrame->endX - tlFrame->startX) / tlFrame->zoomLevel, tlFrame->endY - tlFrame->startY); } /* * Function to draw the new position of the playback head. First, the old line is erased (drawing over it in XOR mode will restore the original canvas * condition), the new line is then drawn in the new position, and the playback head position variable (lastX) is set to the new position. * The parameter newPosition is where the new position is to be. * Called by DrawCanvasEventHandler (canvas.c), DeleteNote (note.c), TimerNotify (play.c) */ void DrawPlaybackHead(newPosition, tlFrame) int newPosition; TimeLineFramePtr tlFrame; { int playbackHeadPosition; int oldPosition; int min, sec; char buf[4]; playbackHeadPosition = (newPosition / tlFrame->zoomLevel) - tlFrame->canvasStart; oldPosition = (tlFrame->lastX / tlFrame->zoomLevel) - tlFrame->canvasStart; if (oldPosition >= 0 && oldPosition <= tlFrame->TimeLineLength) /* Clear the old playback head if necessary */ XDrawLine(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, oldPosition, 0, oldPosition, tlFrame->numberOfApps * 72 + FirstCableYPosition); if (playbackHeadPosition >= 0 && playbackHeadPosition <= tlFrame->TimeLineLength) XDrawLine(tlFrame->dpyDraw, tlFrame->xidDraw, tlFrame->gcLine, playbackHeadPosition, 0, playbackHeadPosition, tlFrame->numberOfApps * 72 + FirstCableYPosition); tlFrame->lastX = newPosition; min = 0; /* Calculate the time to display in the pause marker info window */ sec = newPosition / PixelsPerSecond; if (sec < 0) sec = 0; while (sec >= 60) { min ++; sec -= 60; } sprintf (buf, "%02d", sec); xv_set(tlFrame->PausePopup->IPSecText, PANEL_VALUE, buf, NULL); sprintf (buf, "%4d", min); xv_set(tlFrame->PausePopup->IPMinText, PANEL_VALUE, buf, NULL); }